package tv.geir.slackspace.client;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tv.geir.slackspace.client.SocketPool.SocketPoolEntry;
import tv.geir.slackspace.common.ExecutionFailedException;
import tv.geir.slackspace.common.SlackSpaceException;
import tv.geir.slackspace.communication.InvalidResponseException;
import tv.geir.slackspace.communication.Request;
import tv.geir.slackspace.communication.Response;
import tv.geir.slackspace.space.EntryNotFoundException;

public class SlackClientSocketConnector implements SlackClientConnector {

    /**
     * Logger for this class
     */
	private static Log log = LogFactory.getLog(SlackClientSocketConnector.class);

    private SocketPool socketPool;

    public void connect(String url) {
    	String host = url.substring(0, url.indexOf(':'));
    	int port = Integer.parseInt( url.substring(url.indexOf(':') +1 ));
    	
    	try {
            socketPool = new SocketPool(InetAddress.getByName(host), port);
        } catch (IOException e) {
            throw new SlackSpaceException("Error connecting to SlackSpace", e);
        }
    }
    
    public void disconnect() {

        socketPool.disconnect();
    }

    public SlackClientSocketConnector() {

    }

    /*public SlackClientSocketConnector(InetAddress address, int port) {
    	try {
            socketPool = new SocketPool(address, port);
        } catch (IOException e) {
            throw new SlackSpaceException("Error connecting to SlackSpace", e);
        }
    }*/

    /**
     * Transmits a request to a SlackSpace where it is processed and returns the
     * response from the Space.
     *
     * @param request Request to be executed on space
     * @return response  response to execution
     * @throws ExecutionFailedException Indicates a processing error at server side,
     *                                  usually representing an error response from server.
     * @throws SlackSpaceException      Usually thrown when there is a communication problem
     *                                  with the server. Can also represent any other possible error.
     */
    public Response execute(Request request) throws ExecutionFailedException {

        SocketPoolEntry socket = null;
        try {
            // get socket from pool and open streams
            socket = socketPool.getSocket();

            ObjectOutputStream oos = socket.getOut();
            ObjectInputStream ois = socket.getIn();

            // write request
            oos.writeObject(request);

            // wait for response
            Object o = ois.readObject();

            if (!(o instanceof Response)) {
                throw new InvalidResponseException(
                        "Invalid response: " + o);
            }

            // extract response data
            Response res = (Response) o;

            // check response for errors
            raiseExceptionIfBadResponse( res );

            return res;

        } catch (Throwable t) {
            // wrap exception if necessary
            if (t instanceof SlackSpaceException) {
                throw(SlackSpaceException) t;
            } else {
                throw new SlackSpaceException(t);
            }
        } finally {
            // recycle socket
            if (socket != null) {
                socketPool.returnSocket(socket);
            }
        }
    }

    /**
     * Throws appropriate exceptions if response object represents a failed execution
     * or data is missing.
     * 
     * @param res
     * @throws EntryNotFoundException if entry was not found
     * @throws ExecutionFailedException if any other exception occured at server
     */
    private void raiseExceptionIfBadResponse(Response res) throws EntryNotFoundException, ExecutionFailedException{
        Response.Code resCode = res.getResponseCode();
        Throwable error = res.getError();

        // check that response contains a response code
        if (resCode == null) {
            throw new ExecutionFailedException(
                    "Null response code received: "
                            + res.getResponseCode());
        }

        if (log.isDebugEnabled()) {
            log.debug("Response code: " + resCode);
        }

        // handle the response code.
        switch (resCode) {
            case OK:
                break;
            case ERROR:
                if (error instanceof EntryNotFoundException) {
                    log.debug("No entry matching the template was found");
                }
                if (error != null) {
                    throw new ExecutionFailedException(
                            "Exception caught from server: " + error.getMessage(), error);
                } else {
                    throw new ExecutionFailedException(
                            "Unknown error occured: " + resCode);
                }

            case EVENT:
                break;
            case SHUTDOWN:
                break;
         }
    }
}